
/* Generated by Interface Builder */

#import "Controller.h"

#import <stdio.h>
#import <syscall.h>
#import <pwd.h>
#import <strings.h>
#import <sys/types.h>
#import <netdb.h>
#import <signal.h>
#import <defaults.h>
#import <objc/Storage.h>
#import <soundkit/Sound.h>


@implementation Controller

void killCr(char *c, int len)
{
    int i;
    
    for(i=0;((i<len) && (c[i]!='\0'));i++)
	if (c[i] == '\n')
	    c[i] = '\0';
    return;
}

int membercmp(MemberRecord *r1, MemberRecord *r2)
{
	return ((!strncmp(r1->user,r2->user,STRING_LENGTH)) &&
	    (!strncmp(r1->username,r2->username,STRING_LENGTH)) &&
	    (!strncmp(r1->machine,r2->machine,STRING_LENGTH)));
}

void copymember(MemberRecord *r1, MemberRecord *r2)
{
    strncpy(r1->user,r2->user,STRING_LENGTH);
    strncpy(r1->username,r2->username,STRING_LENGTH);
    strncpy(r1->machine,r2->machine,STRING_LENGTH);
    r1->port = r2->port;
    return;
}

DPSTimedEntryProc networkEntryHandler(DPSTimedEntry num,
					    double now, 
					    char *userData)
{
    [(id)userData doNetworkTimedEntry:(id)userData];
    return 0;
}

DPSTimedEntryProc alarmEntryHandler(DPSTimedEntry num,
					    double now, 
					    char *userData)
{
    [(id)userData doAlarmTimedEntry:(id)userData];
    return 0;
}

- init
{
    [super init];
    alertSound = nil;
    numberAlarms = 0;
    netTimedEntry = 0;
    return self;
}

- addMachineQuit:sender
{
    [NXApp abortModal];
    return self;
}

- addMachineOk:sender
{
    [NXApp stopModal];
    return self;
}

- fetchSound:sender
{
    id pan;
    const char * const tps[2] = {"snd",NULL};
    char str[STRING_LENGTH];
    
    pan = [OpenPanel new];
    [pan allowMultipleFiles:NO];
    
    if ([pan runModalForTypes:tps]) {
	if (alertSound)
	    [alertSound free];
	strncpy(str,[pan filename],STRING_LENGTH);
	if (str && str[0])
	    alertSound = [[Sound alloc] initFromSoundfile:str];
	else
	    alertSound = nil;
	[soundFileTextField setStringValue:str];
	[self setDefaults:soundFileTextField];
    }
    return self;
}

- doAlarmTimedEntry:sender
{
    if (numberAlarms < (MAXALARMS *2)) {
	numberAlarms++;
	[coverButton setState:(![coverButton state])];
    } else {
	numberAlarms = 0;
	DPSRemoveTimedEntry(alarmTimedEntry);
	[coverButton setState:NO];
    }
    return self;
}

- handleAlarm
{    
    if ([noisyAlertSwitch state]) {
	if (alertSound)
	    [alertSound play:self];
	else
	    NXBeep();
    }
    if ([visualAlertSwitch state]) {
	alarmTimedEntry=DPSAddTimedEntry(ALARMENTRYPERIOD,
					(DPSTimedEntryProc)alarmEntryHandler,
					(void *)self,
					 30);
    }
    if ([activateOnAlertSwitch state]) {
	if ([NXApp isHidden])
	    [NXApp unhide:self];
    }
    return self;
}

- doOutput:(char *)msg withSender:(MemberRecord *)owner andBold:(BOOL)bold
{
    int l;
    id p;
    char str[STRING_LENGTH*3+10];
    
    [self getBrowserString:str fromMember:owner];
    sprintf(str,"%s: ",str);
    p = [logText docView];
    l = [p textLength];
    [p setSel:l :l];
    [p replaceSel:str];
    [p setSel:l :[p textLength]];
    if (bold)
	[p setSelFontStyle:NX_BOLD];
    else
	[p setSelFontStyle:NX_ITALIC];
    l = [p textLength];
    [p setSel:l :l];
    [p replaceSel:msg];
    l = [p textLength];
    [p setSel:l :l];
    [p replaceSel:"\n"];
    l = [p textLength];
    [p setSel:l :l];
    return self;
}

- input:sender
{
    id matrix;
    int i,s;
    MemberRecord *temp;
    char str[1024];
        
    i = [sender textLength];
    if (1024 < i)
	i = 1024;
	
    [sender selectAll:self];
    s = [sender getSubstring:str start:0 length:i];
    [sender replaceSel:""];
    str[s] = '\0';
    
    [self doOutput:str withSender:(&me) andBold:YES];
    
    matrix = [memberBrowser matrixInColumn:0];
    
    for(i=0;(i<[matrix cellCount]);i++) {
	if ([[matrix cellAt:i :0] state]) {
	    temp = [memberStore elementAt:i];
	    [speak setSendPort:temp->port];
	    [speak sendMessage:str from:(char *)(&me) length:(sizeof(me))];
	}
    }
    [sender selectText:self];
    return self;
}

- quit:sender
{
    int i;
    MemberRecord *temp;
    for(i=0;(i<[memberStore count]);i++) {
	temp = [memberStore elementAt:i];
	[speak setSendPort:temp->port];
	[speak goodByeIAmLeaving:(char *)(&me) length:(sizeof(me))];
    }
    [memberStore free];
    [listen removePort];
    [listen free];
    [speak free];
    [NXApp terminate:self];
    return self;
}

- (BOOL)gotMemberWithMachine:(const char *)machine
{
    int i;
    MemberRecord *temp;
    for(i=0;(i<[memberStore count]);i++) {
	temp = [memberStore elementAt:i];
	if (!strncmp(temp->machine,machine,STRING_LENGTH))
	   return YES;
    }
    return NO;
}

- (BOOL)gotMember:(MemberRecord *)mem
{
    int i;
    MemberRecord *temp;
    
    for(i=0;(i<[memberStore count]);i++) {
	temp = [memberStore elementAt:i];
	if (membercmp(mem,temp))
	   return YES;
    }
    return NO;
}

- changePref:sender
{
    [self setDefaults:sender];
    return self;
}

- getDefaults:sender
{
    const char *c;
    
    c = NXReadDefault([NXApp appName],"ShowLogin");
    [loginSwitch setState:((c == NULL) || (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"ShowReal");
    [realSwitch setState:((c != NULL) && (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"ShowMachine");
    [machineSwitch setState:((c != NULL) && (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"UseConference");
    [useConferenceSwitch setState:((c == NULL) || (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"UseNetwork");
    [useNetworkSwitch setState:((c != NULL) && (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"UseNoiseAlert");
    [noisyAlertSwitch setState:((c != NULL) && (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"UseVisualAlert");
    [visualAlertSwitch setState:((c == NULL) || (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"ActivateOnAlert");
    [activateOnAlertSwitch setState:((c != NULL) && (!strncmp("YES",c,3)))];
    c = NXReadDefault([NXApp appName],"SoundFile");
    [soundFileTextField setStringValue:c];
    
    return self;
}

- setDefaults:sender
{
    char str[10];
    char param[20];
    const char *c;
    char strn[64];
    
    // Yes, in case you were wondering, this _is_ Evil, not to mention
    // sloppy in the extreme.
    
    if (sender == loginSwitch)
	strncpy(param,"ShowLogin",20);
    else if (sender == realSwitch)
	strncpy(param,"ShowReal",20);
    else if (sender == machineSwitch)
	strncpy(param,"ShowMachine",20);
    else if (sender == useConferenceSwitch)
	strncpy(param,"UseConference",20);
    else if (sender == useNetworkSwitch)
	strncpy(param,"UseNetwork",20);
    else if (sender == noisyAlertSwitch)
	strncpy(param,"UseNoiseAlert",20);
    else if (sender == visualAlertSwitch)
	strncpy(param,"UseVisualAlert",20);
    else if (sender == activateOnAlertSwitch)
	strncpy(param,"ActivateOnAlert",20);
    else if (sender == soundFileTextField) {
	NXWriteDefault([NXApp appName],"SoundFile",[sender stringValue]);
	if (alertSound)
	     [alertSound free];
	
	c = [sender stringValue];
	if (c && c[0]) {
	    strncpy(strn,c,64);
	    alertSound = [[Sound alloc] initFromSoundfile:str];
	}
	return self;
    }
    
    if ([sender state])
	strncpy(str,"YES",10);
    else
	strncpy(str,"NO",10);
    NXWriteDefault([NXApp appName],param,str);
    
    if ((sender == useConferenceSwitch) && ([sender state]) &&
	(NX_ALERTDEFAULT==NXRunAlertPanel("Hey!","Load .Conference File now?",
					   "Ok","Cancel",NULL)))
	    [self doConferenceFile:self];
    if ((sender == useNetworkSwitch) && ([sender state]) &&
	(NX_ALERTDEFAULT==NXRunAlertPanel("Hey!","Scan network now?",
					   "Ok","Cancel",NULL)))
	[self doNetwork:self];
	
    [memberBrowser reloadColumn:0];
    return self;
}

- selectAllMembers:sender
{
    id p;
    int i;
    
    p = [memberBrowser matrixInColumn:0];
    
    for(i=0;(i<[p cellCount]);i++) {
	[p selectCellAt:i :0];
    }
    return self;
}

- connectTo:(const char *)machine
{
    int s;
    MemberRecord *temp;
    int i;
    port_t pt;
    
    if ((!machine) || (!machine[0]) || (machine[0] == '\0'))
	return self;
    for(i=0;(i<[memberStore count]);i++) {
	temp = [memberStore elementAt:i];
	if (!strncmp(machine,temp->machine,STRING_LENGTH))
	    return self;
    }
    
    s=NXRunAlertPanel("Hey!",
	    "Attempt to connect to %s?",
	    "Connect","Do Not Connect",NULL,machine);
    if (s == NX_ALERTDEFAULT) {
	pt = NXPortNameLookup(APPNAME,machine);
	if (PORT_NULL!=pt) {
	    [speak setSendPort:pt];
	    [speak helloIAm:(char *)(&me) length:(sizeof(me))];
	}
    }
    return self;
}

- disconnectFrom:(const char *)machine
{
    int s;
    MemberRecord *temp;
    int i;
    
    for(i=0;(i<[memberStore count]);i++) {
	temp = [memberStore elementAt:i];
	if (!strncmp(machine,temp->machine,STRING_LENGTH)) {
	    s = NXRunAlertPanel("Hey!",
				"Attempt to disconnect from %s?",
				"Disconnect","Stay Connected",NULL,machine);
	    if (s == NX_ALERTDEFAULT) {
		[speak setSendPort:temp->port];
		[speak goodByeIAmLeaving:(char *)&me length:sizeof(me)];
		[memberStore removeAt:i];
		return self;
	    }
	}
    }
    return self;
}

- connectMember:sender
{
    char c[STRING_LENGTH];
    
    [addMachinePanel makeKeyAndOrderFront:self];
    [addMachineField selectText:self];

    if (NX_RUNABORTED == [NXApp runModalFor:addMachinePanel]) {
	[addMachinePanel close];
	return self;
    }
    
    [addMachinePanel close];
    strncpy(c,[addMachineField stringValue],STRING_LENGTH);
    killCr(c,STRING_LENGTH);
    [self connectTo:c];
    [memberBrowser reloadColumn:0];
    return self;
}

- disconnectMember:sender
{
    id p;
    int i;
    char c[STRING_LENGTH];
    MemberRecord *temp;
    
    c[0] = '\0';
    p = [memberBrowser matrixInColumn:0];
    
    for(i=0;(i<[memberStore count]);i++) {    
	temp = [memberStore elementAt:i];
	if ([[p cellAt:i :0] state]) {
	    strncpy(c,temp->machine,STRING_LENGTH);
	    killCr(c,STRING_LENGTH);
	}
    }
    if (c[0]) {
	[self disconnectFrom:c];
    }
    [memberBrowser reloadColumn:0];
    return self;
}

- connectMachine:sender
{
    id p,q;
    char c[STRING_LENGTH];
    
    p = machineBrowser;
    
    q = [[p matrixInColumn:[p selectedColumn]] selectedCell];
    if (q == nil)
	return self;
    strncpy(c,[q stringValue],STRING_LENGTH);
    
    killCr(c,STRING_LENGTH);
    [self connectTo:c];
    [memberBrowser reloadColumn:0];
    return self;
}

- disconnectMachine:sender
{
    id p,q;
    char c[STRING_LENGTH];
    
    p = machineBrowser;
    
    q = [[p matrixInColumn:[p selectedColumn]] selectedCell];
    if (q == nil)
	return self;
    strncpy(c,[q stringValue],STRING_LENGTH);
    
    killCr(c,STRING_LENGTH);
    [self disconnectFrom:c];
    [memberBrowser reloadColumn:0];
    return self;
}

- addMachineToConferenceFile:(const char *)machine
{
    FILE *fd;
    char str[STRING_LENGTH+30];
    char lstr[STRING_LENGTH];
    char *cont;
    
    sprintf(str,"%s/%s",homedir,".ConferenceMachines");
    fd = fopen(str,"r");
    if (fd != NULL) {
	for(cont = fgets(lstr,STRING_LENGTH,fd);
					cont;
	    cont = fgets(lstr,STRING_LENGTH,fd)) {
	    killCr(lstr,STRING_LENGTH);
	    if (!strncmp(lstr,machine,STRING_LENGTH)) {
		fclose(fd);
		return self;
	    }
	}
	fclose(fd);
    }
    fd = fopen(str,"a");
    if (fd != NULL) {
	fputs(machine,fd);
	fputc('\n',fd);
	fclose(fd);
    } else {
	fd = fopen(str,"w");
	if (fd != NULL) {
	    fputs(machine,fd);
	    fputc('\n',fd);
	    fclose(fd);
	}
    }
    return self;
}

- removeMachineFromConferenceFile:(const char *)machine
{
    FILE *fd;
    char str[STRING_LENGTH+30];
    char lstr[STRING_LENGTH];
    int i;
    char *cont;
    char mach[STRING_LENGTH];
    id tempStore;
    
    strncpy(mach,machine,STRING_LENGTH);
    killCr(mach,STRING_LENGTH);
    tempStore = [[Storage alloc] initCount:0
				    elementSize:(sizeof(char) * STRING_LENGTH)
				    description:MACHINESTORAGEDESCRIPTION];
    sprintf(str,"%s/%s",homedir,".ConferenceMachines");
    fd = fopen(str,"r");
    if (fd != NULL) {
	for(cont = fgets(lstr,STRING_LENGTH,fd);
					    cont;
	    cont = fgets(lstr,STRING_LENGTH,fd)) {
	    killCr(lstr,STRING_LENGTH);
	    if (strncmp(lstr,mach,STRING_LENGTH)) {
		[tempStore addElement:lstr];
	    }
	}
	fclose(fd);
    }
    fd = fopen(str,"w");
    if (fd == NULL)
	return self;
    for(i=0;(i<[tempStore count]);i++) {
	fputs([tempStore elementAt:i],fd);
	fputc('\n',fd);
    }
    fclose(fd);
    [tempStore free];
    return self;
}

- addMachine:sender
{
    char c[STRING_LENGTH];
    
    [addMachinePanel makeKeyAndOrderFront:self];
    [addMachineField selectText:self];
    
    if (NX_RUNABORTED == [NXApp runModalFor:addMachinePanel]) {
	[addMachinePanel close];
	return self;
    }
    
    [addMachinePanel close];
    strncpy(c,[addMachineField stringValue],STRING_LENGTH);
    killCr(c,STRING_LENGTH);
    if (c[0]) {
	[self addMachineToConferenceFile:c];
	[machineBrowser reloadColumn:0];
	if (strncmp(me.machine,c,STRING_LENGTH)) {
	    [self connectTo:c];
	    [memberBrowser reloadColumn:0];
	}
    }
    return self;
}

- removeMachine:sender
{
    id p,q;
    char c[STRING_LENGTH];
    
    p = machineBrowser;
    
    q = [[p matrixInColumn:[p selectedColumn]] selectedCell];
    if (q == nil)
	return self;
    strncpy(c,[q stringValue],STRING_LENGTH);
    
    killCr(c,STRING_LENGTH);
    if (c && c[0]) {
	[self removeMachineFromConferenceFile:c];
	[machineBrowser reloadColumn:0];
	if (strncmp(me.machine,c,STRING_LENGTH))
	    [self disconnectFrom:c];
	    [memberBrowser reloadColumn:0];
    }
    return self;
}

- getBrowserString:(char *)str fromMember:(MemberRecord *)mem
{
    str[0] = '\0';
    
    if ([loginSwitch state]) {
	sprintf(str,"%s",mem->user);
    }
    if ([realSwitch state]) {
	if (str[0])
	    sprintf(str,"%s (%s)",str,mem->username);
	else
	    sprintf(str,"(%s)",mem->username);
    }
    if ([machineSwitch state]) {
	if (str[0])
	    sprintf(str,"%s@%s",str,mem->machine);
	else
	    sprintf(str,"%s",mem->machine);
    }
    return self;
}

- yeahRight:sender
{
    if (!strcmp([sender title],"Thomas K. Burkholder")) {
	[sender setTitle:"Waddya want, a picture?"];
    } else {
	[sender setTitle:"Thomas K. Burkholder"];
    }
    return self;
}

- doConferenceFile:sender
{
    char *cont;
    port_t pt;
    FILE *fd;
    char str[STRING_LENGTH+30];
    sprintf(str,"%s/%s",homedir,".ConferenceMachines");
    fd = fopen(str,"r");
    if (fd == NULL)
	return self;

    for(cont = fgets(str,STRING_LENGTH,fd);
				    cont;
	cont = fgets(str,STRING_LENGTH,fd)) {
	killCr(str,STRING_LENGTH);
	if ((![self gotMemberWithMachine:str]) &&
	   (strncmp("localhost",str,STRING_LENGTH)) &&
	   (strncmp("broadcasthost",str,STRING_LENGTH)) &&
	   (strncmp(me.machine,str,STRING_LENGTH))) {
	    pt = NXPortNameLookup(APPNAME,str);
	    if (PORT_NULL!=pt) {
		[speak setSendPort:pt];
		[speak helloIAm:(char *)(&me) length:(sizeof(me))];
	    }
	}
    }
    return self;
}

- doNetworkTimedEntry:sender
{
    port_t pt;
    struct hostent *he;
    
    he = gethostent();
    
    if (he == NULL) {
	DPSRemoveTimedEntry(netTimedEntry);
	netTimedEntry = 0;
	return self;
    }
    
    if ((![self gotMemberWithMachine:he->h_name]) &&
	(strncmp("localhost",he->h_name,STRING_LENGTH)) &&
	(strncmp("broadcasthost",he->h_name,STRING_LENGTH)) &&
	(strncmp(me.machine,he->h_name,STRING_LENGTH))) {
	pt = NXPortNameLookup(APPNAME,he->h_name);
	if (PORT_NULL!=pt) {
	    [speak setSendPort:pt];
	    [speak helloIAm:(char *)(&me) length:(sizeof(me))];
	}
    }
    return self;
}

- doNetwork:sender
{
    sethostent(1);    
    if (!netTimedEntry)
	netTimedEntry = DPSAddTimedEntry(NETWORKENTRYPERIOD,
				     (DPSTimedEntryProc)networkEntryHandler,
				     (void *)self,
				     NX_BASETHRESHOLD);
    return self;
}

- appDidInit:sender
{
    struct passwd *pwent;
    char str[64];
    const char *c;
    
    [self getDefaults:sender];
    
    // Initialize sound alert
    c = [soundFileTextField stringValue];
    if (c && c[0]) {
	strncpy(str,c,64);
	alertSound = [[Sound alloc] initFromSoundfile:str];
    }
    
    // Initialize visual alert
    alertWindow = [NXApp appIcon];
    [[alertWindow setContentView:coverButton] free];
    
    pwent = getpwuid(getuid());
    
    strncpy(homedir,pwent->pw_dir,STRING_LENGTH);
    strncpy(me.user,pwent->pw_name,STRING_LENGTH);
    sscanf(pwent->pw_gecos,"%[^,]",me.username);
    gethostname(me.machine,STRING_LENGTH);
    killCr(me.machine,STRING_LENGTH);

    memberStore = [[Storage alloc] initCount:0
				    elementSize:(sizeof(MemberRecord))
				    description:MEMBERSTORAGEDESCRIPTION];
    speak = [[ConferenceSpeaker alloc] init];
    listen = [[ConferenceListener alloc] init];
    [listen setDelegate:self];
    [listen checkInAs:APPNAME];
    [listen addPort];
    
    if ([useConferenceSwitch state])
	[self doConferenceFile:self];
    [machineBrowser reloadColumn:0];
    
    if ([useNetworkSwitch state])
	[self doNetwork:self];

    [memberBrowser reloadColumn:0];
    
    [[inputText docView] setDelegate:self];
    [[inputText docView] setCharFilter:(NXCharFilterFunc)NXFieldFilter];
    [[inputText window] makeKeyAndOrderFront:self];
    [[inputText docView] selectAll:sender];
    return self;
}

-(int)browser:sender fillMatrix:matrix inColumn:(int)column
{
    id currentCell;
    int i;
    
    if (sender == memberBrowser) {
	MemberRecord *temp;
	char l[STRING_LENGTH*3 + 10];
	
	for(i=0;(i<[memberStore count]);i++) {
	    [matrix addRow];
	    currentCell = [matrix cellAt:i :column];
	    temp = [memberStore elementAt:i];
	    [self getBrowserString:l fromMember:temp];
	    [currentCell setStringValue:l];
	    [currentCell setLeaf:YES];
	    [currentCell setLoaded:YES];
	    [currentCell setTag:i];
	}
	return i;
    } else { // sender is machineBrowser
	FILE *fd;
	char str[STRING_LENGTH+30];
	char lstr[STRING_LENGTH];
	char *cont;
	
	sprintf(str,"%s/%s",homedir,".ConferenceMachines");
	fd = fopen(str,"r");
	if (fd == NULL)
	    return 0;
	for(i = 0,cont = fgets(lstr,STRING_LENGTH,fd);
					cont;
	    cont = fgets(lstr,STRING_LENGTH,fd)) {
		if (lstr && lstr[0] && (lstr[0] != '\n')) {
		    [matrix addRow];
		    currentCell = [matrix cellAt:i:0];
		    [currentCell setStringValue:lstr];
		    [currentCell setLeaf:YES];
		    [currentCell setLoaded:YES];
		    [currentCell setTag:i];
		    i++;
		}
	}
	fclose(fd);
	return i;
    }
}

- textDidEnd:sender endChar:(unsigned short)whyEnd
{
    if (whyEnd != 0)
	[self input:sender];
    return self;
}

- (int)helloIAm:(char *)fr length:(int)len
{
    MemberRecord temp,*mr;
    
    mr = (MemberRecord *)fr;
    
    if ([self gotMember:mr])
	return 0;
    copymember(&temp,mr);
    temp.port = NXPortNameLookup(APPNAME,mr->machine);
    [memberStore addElement:&temp];
    [speak setSendPort:temp.port];
    [speak helloIAm:(char *)(&me) length:(sizeof(me))];
    [memberBrowser reloadColumn:0];
    return 0;
}

- (int)goodByeIAmLeaving:(char *)fr length:(int)len
{
    int i;
    MemberRecord *temp;
    
    for(i=0;(i<[memberStore count]);i++) {
	temp = [memberStore elementAt:i];
	if (membercmp((MemberRecord *)fr,temp)) {
	    [memberStore removeAt:i];
	}
    }
    [memberBrowser reloadColumn:0];
    return 0;
}

- (int)sendMessage:(char *)msg from:(char *)fr length:(int)len
{
    id p;
    
    if ([NXApp isHidden]) {
	[self handleAlarm];
    }
    [self doOutput:msg withSender:(MemberRecord *)fr andBold:NO];
    p = [inputText docView];
    [p setSel:[p textLength] :[p textLength]];
    return 0;
}

@end
